/*
 moxl - Modular XML Library
 Copyright (c) 2007, Malcolm Sparks

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package moxl.xpath.tokenizer;

import java.io.IOException;

import moxl.reader.LocateableReader;
import moxl.tokenizer.CodePage;
import moxl.tokenizer.EndOfFileState;
import moxl.tokenizer.Token;
import moxl.tokenizer.TokenReadyState;
import moxl.tokenizer.TokenType;
import moxl.tokenizer.AbstractTokenizerState;
import moxl.tokenizer.TokenizerStateMachine;

public class StartState extends AbstractTokenizerState
{
	private static AbstractTokenizerState instance = new StartState();

	// TODO: Simplify this more and make it readable
	private final static TokenType[] set = new TokenType[] { XPathTokenTypes.AT_SYMBOL, XPathTokenTypes.DOUBLE_COLON, XPathTokenTypes.OPEN_PARENTHESIS,
	        XPathTokenTypes.OPEN_BRACKET, XPathTokenTypes.OPERATOR, };

	public static AbstractTokenizerState getInstance()
	{
		return instance;
	}

	@Override
	public void accept(TokenizerStateMachine stateMachine, Token token, LocateableReader reader) throws IOException
	{
		int character = reader.read();
		if (character == -1)
		{
			stateMachine.changeState(EndOfFileState.getInstance());
		}
		else if (CodePage.isWhitespace(character))
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.WHITESPACE);
			stateMachine.changeState(ConsumeWhitespaceState.getInstance());
		}
		else if (character == '(')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPEN_PARENTHESIS);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == ')')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.CLOSE_PARENTHESIS);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '[')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPEN_BRACKET);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == ']')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.CLOSE_BRACKET);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '.')
		{
			token.append(character);
			stateMachine.changeState(PeriodOrDoublePeriodState.getInstance());
		}
		else if (character == '@')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.AT_SYMBOL);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == ',')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.COMMA);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == ':')
		{
			token.append(character);
			stateMachine.changeState(ExpectingSecondColonState.getInstance());
		}
		else if (character == '/')
		{
			token.append(character);
			stateMachine.changeState(SlashOrDoubleSlashState.getInstance());
		}
		else if (character == '|')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPERATOR);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '+')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPERATOR);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '-')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPERATOR);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '=')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.OPERATOR);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '!')
		{
			token.append(character);
			stateMachine.changeState(PossiblyNotEqualOperatorState.getInstance());
		}
		else if (character == '<')
		{
			token.append(character);
			stateMachine.changeState(LessThanOrLessThanEqualState.getInstance());
		}
		else if (character == '>')
		{
			token.append(character);
			stateMachine.changeState(MoreThanOrMoreThanEqualState.getInstance());
		}
		else if (character == '"')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.LITERAL);
			stateMachine.changeState(DoubleQuotedLiteralState.getInstance());
		}
		else if (character == '\'')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.LITERAL);
			stateMachine.changeState(SingleQuotedLiteralState.getInstance());
		}
		else if (character == '*')
		{
			token.append(character);
			// This is either a MultiplyOperator or a NameTest
			Token precedingToken = stateMachine.getPreviousToken();
			if (isPrecedingTokenNotInSet(precedingToken))
			{
				token.setTokenType(XPathTokenTypes.OPERATOR);
			}
			else
			{
				token.setTokenType(XPathTokenTypes.NAME_TEST);
			}
			stateMachine.changeState(TokenReadyState.getInstance());
		}
		else if (character == '$')
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.VARIABLE_REFERENCE);
			stateMachine.changeState(VariableReferenceExpectingNameState.getInstance());
		}
		else if (CodePage.isDigit(character))
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.NUMBER);
			stateMachine.changeState(InNumberState.getInstance());
		}
		else if (CodePage.isLetter(character) || character == '_')
		{
			token.append(character);
			stateMachine.changeState(XPathInNameOrNodeTypeState.getInstance());
		}
		else
		{
			token.append(character);
			token.setTokenType(XPathTokenTypes.ILLEGAL_CHARACTER);
			stateMachine.changeState(TokenReadyState.getInstance());
		}
	}

	public static boolean isPrecedingTokenNotInSet(Token token)
	{
		if (token == null)
		{
			return false;
		}
		TokenType type = token.getType();
		for (int i = 0; i < set.length; i++)
		{
			if (type.equals(set[i]))
			{
				return false;
			}
		}
		return true;
	}

}
